/*
* CubeNode.java
*
* A cube-shaped UI component upon whose faces other nodes may placed.
*
* Developed by James L. Weaver (jim.weaver#javafxpert.com) to demonstrate the
* use of 3D features in the JavaFX 2.0 API
*/
package javafxpert.cube;
import javafx.animation.Interpolator;
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.animation.TimelineBuilder;
import javafx.application.Platform;
import javafx.beans.InvalidationListener;
import javafx.beans.Observable;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Node;
import javafx.scene.Parent;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.input.MouseEvent;
import javafx.scene.transform.Rotate;
import javafx.scene.transform.RotateBuilder;
import javafx.scene.transform.TranslateBuilder;
import javafx.util.Duration;
import javafxpert.model.CubeModel;
/**
*
* @author Jim Weaver
*/
public class CubeNode extends Parent {
static double HOME_ANGLE_X = 30.0;
static double HOME_ANGLE_Y = -40.0;
static double MIN_ANGLE_X = -70.0;
static double MAX_ANGLE_X = 70.0;
static double MIN_ANGLE_Y = -720.0;
static double MAX_ANGLE_Y = 720.0;
static double MIN_TRANSLATE_Z = 0.0;
static double MAX_TRANSLATE_Z = 10000.0;
CubeModel cubeModel = CubeModel.instance;
CubeFace rearFace;
CubeFace bottomFace;
CubeFace leftFace;
CubeFace rightFace;
CubeFace topFace;
CubeFace frontFace;
SimpleDoubleProperty angleX = new SimpleDoubleProperty(0);
SimpleDoubleProperty angleY = new SimpleDoubleProperty(0);
public CubeNode(CubeModel model) {
angleX.addListener(new InvalidationListener() {
@Override
public void invalidated(Observable ov) {
arrangeFacesZOrder();
}
});
angleY.addListener(new ChangeListener<Object>() {
public void changed(ObservableValue<?> ov, Object oldValue,
Object newValue) {
arrangeFacesZOrder();
}
});
rearFace = new CubeFace(cubeModel, CubeFace.REAR_FACE);
rearFace.getTransforms().setAll(
TranslateBuilder.create().x(0).y(0).z(CubeFace.edgeLength)
.build(),
RotateBuilder.create().angle(180.0).axis(Rotate.Y_AXIS)
.pivotX(CubeFace.edgeLength / 2).build());
bottomFace = new CubeFace(cubeModel, CubeFace.BOTTOM_FACE);
bottomFace.getTransforms().setAll(
TranslateBuilder.create().x(0).y(0).z(CubeFace.edgeLength)
.build(),
RotateBuilder.create().angle(90.0).axis(Rotate.X_AXIS)
.pivotY(CubeFace.edgeLength).build());
leftFace = new CubeFace(cubeModel, CubeFace.LEFT_FACE);
leftFace.getTransforms().setAll(
TranslateBuilder.create().x(0).y(0).z(CubeFace.edgeLength)
.build(),
RotateBuilder.create().angle(90.0).axis(Rotate.Y_AXIS)
.pivotX(0).build());
rightFace = new CubeFace(cubeModel, CubeFace.RIGHT_FACE);
rightFace.getTransforms().setAll(
TranslateBuilder.create().x(0).y(0).z(CubeFace.edgeLength)
.build(),
RotateBuilder.create().angle(-90.0).axis(Rotate.Y_AXIS)
.pivotX(CubeFace.edgeLength).build());
topFace = new CubeFace(cubeModel, CubeFace.TOP_FACE);
topFace.getTransforms().setAll(
TranslateBuilder.create().x(0).y(0).z(CubeFace.edgeLength)
.build(),
RotateBuilder.create().angle(-90.0).axis(Rotate.X_AXIS)
.pivotX(0).build());
frontFace = new CubeFace(cubeModel, CubeFace.FRONT_FACE);
frontFace.getTransforms().setAll(
TranslateBuilder.create().x(0).y(0).z(0).build());
getChildren().addAll(rearFace, topFace, leftFace, rightFace,
bottomFace, frontFace);
Rotate xRotate;
Rotate yRotate;
getTransforms().setAll(
xRotate = RotateBuilder.create().axis(Rotate.X_AXIS)
.pivotX(CubeFace.edgeLength * 0.5)
.pivotY(CubeFace.edgeLength * 0.5)
.pivotZ(CubeFace.edgeLength * 0.5).build(),
yRotate = RotateBuilder.create().axis(Rotate.Y_AXIS)
.pivotX(CubeFace.edgeLength * 0.5)
.pivotY(CubeFace.edgeLength * 0.5)
.pivotZ(CubeFace.edgeLength * 0.5).build());
xRotate.angleProperty().bind(angleX);
yRotate.angleProperty().bind(angleY);
setOnMousePressed(new EventHandler<MouseEvent>() {
public void handle(MouseEvent me) {
dragPressedAngleX = angleX.getValue();
dragPressedAngleY = angleY.getValue();
dragStartOffsetX = me.getScreenX()
- getScene().getWindow().getX();
dragStartOffsetY = me.getScreenY()
- getScene().getWindow().getY();
}
});
setOnMouseDragged(new EventHandler<MouseEvent>() {
public void handle(MouseEvent me) {
if (me.isControlDown()) {
getScene().getWindow().setX(
me.getScreenX() - dragStartOffsetX);
getScene().getWindow().setY(
me.getScreenY() - dragStartOffsetY);
} else if (me.isAltDown()) {
double curTranslateZ = getTranslateZ();
double proposedTranslateZ = curTranslateZ
+ ((me.getScreenY() - getScene().getWindow().getY()) - dragStartOffsetY)
* 10;
if (proposedTranslateZ > MAX_TRANSLATE_Z) {
setTranslateZ(MAX_TRANSLATE_Z);
} else if (proposedTranslateZ < MIN_TRANSLATE_Z) {
setTranslateZ(MIN_TRANSLATE_Z);
} else {
setTranslateZ(proposedTranslateZ);
}
} else {
angleY.setValue((((me.getScreenX() - getScene().getWindow()
.getX()) - dragStartOffsetX))
/ 3
* -1
+ dragPressedAngleY);
angleX.setValue((((me.getScreenY() - getScene().getWindow()
.getY()) - dragStartOffsetY))
/ 3
+ dragPressedAngleX);
}
}
});
setOnKeyPressed(new EventHandler<KeyEvent>() {
public void handle(KeyEvent ke) {
if (ke.getCode() == KeyCode.SPACE) {
if (cubeModel.mapOpacity.getValue() == 0.0) {
showMapTimeline.playFromStart();
} else {
hideMapTimeline.playFromStart();
}
} else if (ke.getCode() == KeyCode.ESCAPE) {
Platform.exit();
}
}
});
}
final void arrangeFacesZOrder() {
rearFace.zPos.setValue(CubeFace.radius
* Math.cos(Math.toRadians(angleY.getValue() + 0)));
bottomFace.zPos.setValue(CubeFace.radius
* Math.cos(Math.toRadians(angleX.getValue() + 270)));
leftFace.zPos.setValue(CubeFace.radius
* Math.cos(Math.toRadians(angleY.getValue() + 270)));
rightFace.zPos.setValue(CubeFace.radius
* Math.cos(Math.toRadians(angleY.getValue() + 90)));
topFace.zPos.setValue(CubeFace.radius
* Math.cos(Math.toRadians(angleX.getValue() + 90)));
frontFace.zPos.setValue(CubeFace.radius
* Math.cos(Math.toRadians(angleY.getValue() + 180)));
FXCollections.sort(getChildren(), new CubeFaceComparator());
}
public void goHomePosition() {
Timeline homeTimeline = TimelineBuilder
.create()
.keyFrames(
new KeyFrame(new Duration(1000.0), new KeyValue(angleX,
HOME_ANGLE_X, Interpolator.EASE_BOTH),
new KeyValue(angleY, HOME_ANGLE_Y,
Interpolator.EASE_BOTH))).build();
homeTimeline.play();
}
public Timeline showMapTimeline = TimelineBuilder
.create()
.keyFrames(
new KeyFrame(new Duration(0.0),
new EventHandler<ActionEvent>() {
public void handle(javafx.event.ActionEvent t) {
goHomePosition();
}
}, new KeyValue(cubeModel.mapOpacity, 0.0,
Interpolator.LINEAR)),
new KeyFrame(new Duration(1000.0), new KeyValue(
cubeModel.mapOpacity, 0.7, Interpolator.EASE_BOTH)))
.build();
public Timeline hideMapTimeline = TimelineBuilder
.create()
.keyFrames(
new KeyFrame(new Duration(0.0),
new EventHandler<ActionEvent>() {
public void handle(javafx.event.ActionEvent t) {
// goHomePosition();
}
}, new KeyValue(cubeModel.mapOpacity, 0.7,
Interpolator.LINEAR)),
new KeyFrame(new Duration(1000.0), new KeyValue(
cubeModel.mapOpacity, 0.0, Interpolator.EASE_BOTH)))
.build();
public Node frontNode;
public Node rearNode;
public Node leftNode;
public Node rightNode;
public Node topNode;
public Node bottomNode;
double dragPressedAngleX;
double dragPressedAngleY;
double dragStartOffsetX;
double dragStartOffsetY;
}